package com.nikolaj.kuzan.controller;

import com.nikolaj.kuzan.utils.DataComparator;
import com.nikolaj.kuzan.utils.DataTransformer;
import com.nikolaj.kuzan.utils.DoubleConverter;
import com.nikolaj.kuzan.utils.Printer;
import org.neuroph.core.Neuron;
import org.neuroph.core.data.DataSet;
import org.neuroph.core.data.DataSetRow;
import org.neuroph.nnet.Hopfield;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.*;

public class Controller {

    private final int IMG_SIZE;
    private DataTransformer dataTransformer = new DataTransformer();

    private Hopfield myHopfield;
    private DataSet trainingSet;
    private List<Double[]> trainingList;

    private List<Integer> listOfAllRandomValues;
    List<Integer> listOfRandomValuesForOneTestOperation;
    private Map<Integer, Integer> timeOfCalculatingMap = new TreeMap<>();
    private long calcDuration;
    private int percentageOfDeletedNeurons;


    public Controller() {
        Map<Integer, Integer> linkPercentWithImageSize = new HashMap<>();
        linkPercentWithImageSize.put(0, 32);
        linkPercentWithImageSize.put(6, 31);
        linkPercentWithImageSize.put(12, 30);
        linkPercentWithImageSize.put(18, 29);
        IMG_SIZE = 50;
    }

    public Map<Integer, Integer> getTimeOfCalculatingMap() {
        return timeOfCalculatingMap;
    }

    public void resetTimeOfCalculationMap(){
        timeOfCalculatingMap.clear();
    }

    public int getCalcDuration(){
        return (int) calcDuration;
    }



    public Double[] imgFileToInputVector(File file, boolean croppImage, int imageSize) throws IOException {

        BufferedImage image = ImageIO.read(file);
        Double[] inputVector = dataTransformer.imgToInputVector(image, croppImage, imageSize);
        return inputVector;

    }

    public List<Double[]> imgFilesToInputVectors(List<File> files, boolean croppImage, int imageSize) throws IOException {
        List<Double[]> inputVectors = new ArrayList<>();
        for (File tempFile : files) {
            inputVectors.add(imgFileToInputVector(tempFile, croppImage, imageSize));
        }
        return inputVectors;
    }

    public DataSet addInputVectorToTrainingSet(Double[] inputVector) {
        //if training set isn't exist then create new training set
        if (trainingSet == null) {
            trainingSet = new DataSet(inputVector.length);
        }

        double[] dInputVector = DoubleConverter.objectDoubleArrToDoubleArr(inputVector);
        //add training element to exist training set
        trainingSet.addRow(new DataSetRow(dInputVector));
        return trainingSet;
    }

    public DataSet addInputVectorsToTrainingSet(List<Double[]> inputVectors) {

        if (trainingSet == null) {
            trainingSet = new DataSet(inputVectors.get(0).length);
        }

        for (Double[] vector : inputVectors) {
            double[] dInputVector = DoubleConverter.objectDoubleArrToDoubleArr(vector);
            trainingSet.addRow(new DataSetRow(dInputVector));
        }
        return trainingSet;
    }

    public Hopfield trainNetwork(DataSet trainingSet, int countOfLearningCycles) {

        // create hopfield network
        if (myHopfield == null && trainingSet.getInputSize() > 0) {
            myHopfield = new Hopfield(trainingSet.getInputSize());
        } else {
            //throw new RuntimeException("Cann't create training set");
        }


        /**
         *  generating random values list for removing connections between neurons
         *  in this positions in deleteNeurons() method
         */

        Random random = new Random();
        int randomVal;
        listOfAllRandomValues = new ArrayList<>();

        for(int i = 0; i < trainingSet.getInputSize(); i++){
            randomVal = random.nextInt(myHopfield.getLayerAt(0).getNeuronsCount());

            while (true){
                if (listOfAllRandomValues.contains(randomVal)){
                    randomVal = random.nextInt(myHopfield.getLayerAt(0).getNeuronsCount());
                } else{
                    listOfAllRandomValues.add(randomVal);
                    break;
                }
            }
        }
        /**
         * for remove input connections from neurons
         */
        //Collections.sort(listOfAllRandomValues);


        /**
         * end of generating random values
         */


        /**
         * learning network on the training set
         */
        for (int i = 0; i < countOfLearningCycles; i++){
            myHopfield.learn(trainingSet);
        }
        return myHopfield;
    }

    public Hopfield deleteNeurons(Hopfield network, int percentageOfNeurons, DeleteFrom deleteFrom){

        //TODO добавить проверку для всех значений, введенных пользователем (не только те, которые корректны)
        //int newImgSize = linkPercentWithImageSize.get(percentageOfNeurons);

        this.percentageOfDeletedNeurons = percentageOfNeurons;
        int countOfNeuronsForDelete = (int) Math.round((IMG_SIZE*IMG_SIZE)/100.0*percentageOfNeurons);

        //--------------------------------------------------------

        listOfRandomValuesForOneTestOperation = new ArrayList<>();
        for (int i = 0; i < countOfNeuronsForDelete; i++){

            int randomVal = listOfAllRandomValues.get(i);
            listOfRandomValuesForOneTestOperation.add(randomVal);

            /**
             * for remove all output connections from neurons
             */


            //network.getLayerAt(0).removeNeuronAt(randomVal);
            network.getLayerAt(0).getNeuronAt(randomVal).removeAllOutputConnections();


            /**
             * for remove all input conntctions from neurons
             */
            //network.getLayerAt(0).getNeuronAt(randomVal).removeAllInputConnections();
        }

        //-------------------------------------------------------
        double[] dNewWeightMatrix = DoubleConverter.objectDoubleArrToDoubleArr(network.getWeights());
        //myHopfield = new Hopfield(myHopfield.getLayerAt(0).getNeuronsCount());
        //myHopfield.setWeights(dNewWeightMatrix);
        //-------------------------------PRINTING TO CONSOLE-------------------------------------------------//
        System.out.println("neurons count in deleteNeurons - " + network.getLayerAt(0).getNeuronsCount());
        //---------------------------------END OF PRINTING---------------------------------------------------//


        /**
         * for method of removing neurons from network
         */

        /*
        Collections.sort(listOfRandomValuesForOneTestOperation);

        List<Neuron> inputNeurons = new ArrayList<>(Arrays.asList(network.getInputNeurons()));
        List<Neuron> outputNeurons = new ArrayList<>(Arrays.asList(network.getOutputNeurons()));

        for (int i = listOfRandomValuesForOneTestOperation.size()-1; i >= 0; i--){
            network.getLayerAt(0).removeNeuronAt(listOfRandomValuesForOneTestOperation.get(i));
            inputNeurons.remove(listOfRandomValuesForOneTestOperation.get(i).intValue());
            outputNeurons.remove(listOfRandomValuesForOneTestOperation.get(i).intValue());
        }

        Neuron[] inputNeuronsArr = new Neuron[inputNeurons.size()];
        inputNeurons.toArray(inputNeuronsArr);

        Neuron[] outputNeuronsArr = new Neuron[outputNeurons.size()];
        inputNeurons.toArray(outputNeuronsArr);

        network.setInputNeurons(inputNeuronsArr);
        network.setOutputNeurons(outputNeuronsArr);
        */





        trainingList = new ArrayList<>();
        for (int i = 0; i < trainingSet.size(); i++) {

            double[] tempArr = trainingSet.getRowAt(i).getInput();
            Double[] DTempArr = DoubleConverter.doubleArrToObjectDoubleArr(tempArr);

            List<Double> tempList = new ArrayList<>(Arrays.asList(DTempArr));
            for (int j = 0; j < countOfNeuronsForDelete; j++){
                //tempList.set(listOfRandomValuesForOneTestOperation.get(j), -1.0);
            }

            /*
            Iterator<Double> it = tempList.iterator();
            while (it.hasNext()) {
                if (it.next().equals(-1.0)) {
                    it.remove();
                    // If you know it's unique, you could `break;` here
                }
            }
            */

            DTempArr = new Double[tempList.size()];
            tempList.toArray(DTempArr);

            trainingList.add(DTempArr);
        }

        return network;
    }

    public Double[] testNetwork(Hopfield network, File imgFileForTest, int imgSizeForTest, boolean croppImage,
                                int percentOfDeletedNeurons, DeleteFrom deleteFrom) throws IOException {

        System.out.println();
        System.out.println("Testing network");
        System.out.println("Original image from panel - 32x32");

        Double[] inputForTest = imgFileToInputVector(imgFileForTest, croppImage, imgSizeForTest);


        List<Double> tempList = new ArrayList<>(Arrays.asList(inputForTest));


        /*
        for (int i = 0; i < listOfAllRandomValues.size(); i++){
            if (listOfAllRandomValues.get(i) < tempList.size()-1){
                tempList.set(listOfAllRandomValues.get(i)+1, (tempList.get(listOfAllRandomValues.get(i))+tempList.get(listOfAllRandomValues.get(i)+1))/2);
            }
            tempList.remove(listOfAllRandomValues.get(i).intValue());
        }*/
        for (int j = 0; j < listOfRandomValuesForOneTestOperation.size(); j++){
           // tempList.set(listOfRandomValuesForOneTestOperation.get(j), -1.0);
        }

        /*
        Iterator<Double> it = tempList.iterator();
        while (it.hasNext()) {
            if (it.next().equals(-1.0)) {
                it.remove();
                // If you know it's unique, you could `break;` here
            }
        }
        */
        inputForTest = new Double[tempList.size()];
        tempList.toArray(inputForTest);


        double[] dInput = DoubleConverter.objectDoubleArrToDoubleArr(inputForTest);

        System.out.println(dInput.length);
        System.out.println(network.getInputsCount());
        System.out.println(network.getLayerAt(0).getNeuronsCount());
        System.out.println(network.getOutputsCount());



        network.setInput(dInput);


        long beginOfCalculating = System.currentTimeMillis();
        for (int i = 0; i < 20; i++){
            network.calculate();
        }
        long endOfCalculating = System.currentTimeMillis();
        calcDuration = (endOfCalculating - beginOfCalculating);

        timeOfCalculatingMap.put(percentageOfDeletedNeurons, (int) calcDuration);

        Double[] networkOutput = DoubleConverter.doubleArrToObjectDoubleArr(network.getOutput());






        System.out.println("CALCULATION DURATION - " + calcDuration);
        System.out.println("-------------------NETWORK OUTPUT-----------------");
        System.out.print("output in controller - " + networkOutput.length);
        Printer.printImg(networkOutput, imgSizeForTest);
        System.out.println("-*-*-*-*-*-*-*-*-NETWORK OUTPUT-*-*-*-*-*-*-*-*-*-*-*");




        // ----------- For Input Connection Remove method only --------------//
        if (listOfRandomValuesForOneTestOperation != null){
            for (int value : listOfRandomValuesForOneTestOperation){
                if (value < networkOutput.length - 1){
                   // networkOutput[value] = -1.0;
                }
            }
        }

        return networkOutput;
    }


    public List<Double> compareVectors(List<File> trainingFiles, Double[] networkOutput, int percentOfDeletedNeurons) throws IOException {

        System.out.println();
        System.out.println("-------------OUTPUT WILL BE COMPARED WITH NEXT IMAGES---------------");
        //trainingList = imgFilesToInputVectors(trainingFiles, false, newImgSize);
        System.out.println("//-----------OUTPUT WILL BE COMPARED WITH NEXT IMAGES-------------//");

        return DataComparator.compareVectors(trainingList, networkOutput);
    }

    public List<Double> compareVectors(List<Double[]> originalVectors, Double[] noisedVector){

        /*
        for (Double[] origVector : originalVectors){
            for (int i = 0; i < listOfRandomValuesForOneTestOperation.size(); i++){
                origVector[listOfRandomValuesForOneTestOperation.get(i)] = -1.0;
            }
        }
        */
        return DataComparator.compareVectors(originalVectors, noisedVector);
    }

    public BufferedImage imgVectorToImg(Double[] vector, int imageSize){
        return dataTransformer.imgVectorToImg(vector, imageSize);
    }
}




